﻿using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Autofac;
using Autofac.Extensions.DependencyInjection;
using Autofac.Extras.DynamicProxy;
using Common;
using Core.Extensions.AOP;
using easyui.AuthHelper;
using easyui.Filter;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
using Respository.Base;

namespace easyui
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            
            

            var basePath = Microsoft.DotNet.PlatformAbstractions.ApplicationEnvironment.ApplicationBasePath;
            
            services.AddSingleton(new Appsettings(Configuration));
            services.AddSingleton(new Appsettings(basePath));

            //系统自带
            //services.Configure<CookiePolicyOptions>(options =>
            //{
            //    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            //    options.CheckConsentNeeded = context => true;
            //    options.MinimumSameSitePolicy = SameSiteMode.None;
            //});

           
            #region 1、基于策略的授权（简单版）
            // 1【授权】、这个和上边的异曲同工，好处就是不用在controller中，写多个 roles 。
            // 然后这么写 [Authorize(Policy = "Admin")]
            services.AddAuthorization(options =>
            {
                options.AddPolicy("Client", policy => policy.RequireRole("Client").Build());
                options.AddPolicy("Admin", policy => policy.RequireRole("Admin").Build());
                options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));
                options.AddPolicy("A_S_O", policy => policy.RequireRole("Admin", "System", "Others"));
            });
            #endregion

            #region JWT Token Service

            //读取配置文件
            var audienceConfig = Configuration.GetSection("Audience");
            var symmetricKeyAsBase64 = audienceConfig["Secret"];
            var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
            var signingKey = new SymmetricSecurityKey(keyByteArray);

            //令牌验证参数
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = audienceConfig["Issuer"],//发行人
                ValidateAudience = true,
                ValidAudience = audienceConfig["Audience"],//订阅人
                ClockSkew = TimeSpan.Zero,//这个是缓冲过期时间，也就是说，即使我们配置了过期时间，这里也要考虑进去，过期时间+缓冲，默认好像是7分钟，你可以直接设置为0
                RequireExpirationTime = true
            };
            var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);

            var permission =  new List<PermissionItem>();

            //角色与接口的权限要求参数
            var permissionRequirement = new PermissionRequirement(
                "api/denied",/*拒绝授权的跳转地址（目前无用）*/
                permission,
                ClaimTypes.Role,//基于角色授权
                audienceConfig["issuer"],
                audienceConfig["Audience"],
                signingCredentials,
                expiration:TimeSpan.FromSeconds(60*60)//接口过期时间，注意这里没有了缓冲时间，可以自定义，在上边的tokenValidationParameters的 ClockSkew
                );

            //1.复杂的策略授权，具体规则，已经对应的权限策略，比如公司的不同权限的门禁卡
            services.AddAuthorization(options => {
          
                //自定义基于策略的授权权限
                options.AddPolicy("Permission", policy => policy.Requirements.Add(permissionRequirement));

            })
            //2.必须配置认证服务，这里默认jwtBearer，比如光有卡没用，得能识别他们
            .AddAuthentication(x=> {
                x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

            })
            //3.针对JWT配置，比如门禁卡如何识别的，是放射卡，还是磁卡
            .AddJwtBearer(o=> {
                o.TokenValidationParameters = tokenValidationParameters;
                o.Events = new JwtBearerEvents
                {
                    OnChallenge = context =>
                    {
                        context.Response.Headers.Add("Token-Error", context.ErrorDescription);
                        return Task.CompletedTask;
                    },
                    OnAuthenticationFailed = context =>
                    {
                        var token = context.Request.Headers["Authorization"].ToString() == null ? "" : context.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
                        var jwtToken = (new JwtSecurityTokenHandler()).ReadJwtToken(token);

                        if (jwtToken.Issuer != audienceConfig["Issuer"])
                        {
                            context.Response.Headers.Add("Token-Error-Iss", "issuer is wrong!");
                        }

                        if (jwtToken.Audiences.FirstOrDefault() != audienceConfig["Audience"])
                        {
                            context.Response.Headers.Add("Token-Error-Aud", "Audience is wrong!");
                        }
                        
                        // 如果过期，则把<是否过期>添加到，返回头信息中
                        if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                        {
                            context.Response.Headers.Add("Token-Expired", "true");
                        }
                        return Task.CompletedTask;
                    }
                };
            });

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
            //自定义的授权处理器匹配官方的处理器接口，这样当系统处理授权的时候，就会直接访问我们自定义的授权处理
            services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
            //将授权必要类注入生命周期内
            services.AddSingleton(permissionRequirement);

            #endregion
            services.AddMvc(option => {
                option.Filters.Add(new AuthorizeFilter("Permission"));//全局认证
                option.Filters.Add(typeof(GlobalExceptionFilter));//注入全局异常捕获
            }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);


            #region Autofac
            //实例化 Autofac 容器
            var builder = new ContainerBuilder();

            builder.RegisterType<LogAOP>();

            //没有接口对应的注入
            var assemblyServices =  Assembly.Load("GBLServices");

            //builder.RegisterAssemblyTypes(assemblyServices);//.AsImplementedInterfaces()

            builder.RegisterAssemblyTypes(assemblyServices)
                .InstancePerLifetimeScope()
                //.EnableInterfaceInterceptors()//引入 Autofac.Extras.DynamicProxy 拦截的对象是接口代理模式
                .EnableClassInterceptors()//引入 Autofac.Extras.DynamicProxy 拦截的对象是单纯的类
                .InterceptedBy(typeof(LogAOP));//可以直接替换拦截器

            //有接口的对应注入
            //var repositoryDllFile = Path.Combine(basePath, "Repository.dll");
            //var assemblysRepository = Assembly.LoadFrom(repositoryDllFile);
            //builder.RegisterAssemblyTypes(assemblysRepository).AsImplementedInterfaces();

            builder.RegisterGeneric(typeof(RepositoryBase<>)).As(typeof(IRepositoryBase<>)).InstancePerDependency(); //InstancePerDependency//每次调用，都会重新实例化对象；每次请求都创建一个新的对象； 

            //将services填充到Autoface容器生成器中
            builder.Populate(services);
            var ApplicationContainer = builder.Build();
            
            #endregion


            return new AutofacServiceProvider(ApplicationContainer);
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            //app.UseCookiePolicy();
            app.UseAuthentication();

            app.UseMvc(routes =>
            {
                //routes.MapRoute(
                //    name: "default",
                //    template: "{controller=Home}/{action=Index}/{id?}");
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Login}/{action=Login}/{id?}");
            });
        }
    }
}
